﻿using Microsoft.Win32;
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;

namespace FoxProxy
{
    public partial class formFoxProxy : Form
    {
        const string ProxyPattern = @"<tr><td>[\s\S]*?<script type=text/javascript>document.write[\s\S]*?</td></tr>";
        const string IPPattern = @"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b";
        const string PortPattern = @"([\+]\w)+";
        const string ProxySettingRegKey = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings";
        const string ProxyEnableRegKey = "ProxyEnable";
        const string ProxyServerRegKey = "ProxyServer";
        const string DefaultConnectionSettingRegKey = "DefaultConnectionSettings";
        const string EnableProxyButtonText = "Enable Proxy";
        const string DisableProxyButtonText = "Disable Proxy";
        const string StatusNotAvailable = "Not available";
        const string StatusNotTested = "Not Tested";
        int currentProxyPageIndex = 1;

        Regex regex = new Regex(ProxyPattern, RegexOptions.IgnoreCase);
        Regex regexIP = new Regex(IPPattern);
        Regex regexPort = new Regex(PortPattern);

        public formFoxProxy()
        {
            InitializeComponent();
            RegisterEvents();
        }

        void RegisterEvents()
        {
            this.Load += formFoxProxy_Load;
            this.btnSetProxy.Click += btnSetProxy_Click;
            this.btnEnableProxy.Click += btnEnableProxy_Click;
            this.lvProxy.ColumnClick += lvProxy_ColumnClick;
            this.btnExport.Click += btnExport_Click;
            this.btnMore.Click += btnMore_Click;
            this.btnImport.Click += btnImport_Click;
        }

        void btnImport_Click(object sender, EventArgs e)
        {
            if (ofdImport.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                string fileName = ofdImport.FileName;
                string[] proxyStrings = File.ReadAllLines(fileName);
                Collection<Proxy> proxies = new Collection<Proxy>();
                foreach (string proxyString in proxyStrings)
                {
                    string[] ipandport = proxyString.Split(new char[] { ':' });
                    Proxy proxy = new Proxy();
                    proxy.IP = ipandport[0];
                    proxy.Port = ipandport[1];
                    proxies.Add(proxy);
                }
                lvProxy.Items.Clear();
                BindProxy(proxies);
                TestProxySpeed();
            }
        }

        void btnMore_Click(object sender, EventArgs e)
        {
            if (currentProxyPageIndex < 11)
            {
                currentProxyPageIndex++;
            }
            else
            {
                btnMore.Enabled = false;
            }
            DownloadProxy(currentProxyPageIndex);
            TestProxySpeed();
        }

        void btnExport_Click(object sender, EventArgs e)
        {
            StringBuilder exported = new StringBuilder();
            foreach (ListViewItem item in lvProxy.Items)
            {
                int time;
                if (int.TryParse(item.SubItems[3].Text, out time) && time < nudExport.Value * 1000)
                {
                    exported.AppendFormat("{0}:{1}\r\n", item.SubItems[1].Text, item.SubItems[2].Text);
                }
            }
            File.WriteAllText(string.Format("ExportedProxy_{0}.txt", DateTime.Now.ToString("yyyy_MM_dd_HH")), 
                exported.ToString(),
                Encoding.UTF8);
            MessageBox.Show("Exported Successfully");
        }

        void lvProxy_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            lvProxy.ListViewItemSorter = new ListViewItemComparer(e.Column);
        }

        void formFoxProxy_Load(object sender, EventArgs e)
        {
            LoadCurrentProxy();
            DownloadProxy(currentProxyPageIndex);
            TestProxySpeed();
        }

        void btnEnableProxy_Click(object sender, EventArgs e)
        {
            RegistryKey reg = Registry.CurrentUser.OpenSubKey(ProxySettingRegKey, true);
            if (btnEnableProxy.Text == EnableProxyButtonText)
            {
                reg.SetValue(ProxyEnableRegKey, 1, RegistryValueKind.DWord);
                btnEnableProxy.Text = DisableProxyButtonText;
            }
            else
            {
                reg.SetValue(ProxyEnableRegKey, 0, RegistryValueKind.DWord);
                btnEnableProxy.Text = EnableProxyButtonText;
            }
            reg.Close();
            NotifyRegKeyChanges();
        }

        void btnSetProxy_Click(object sender, EventArgs e)
        {
            if (lvProxy.SelectedItems.Count > 0)
            {
                Proxy proxy = lvProxy.SelectedItems[0].Tag as Proxy;
                if (proxy != null)
                {
                    RegistryKey reg = Registry.CurrentUser.OpenSubKey(ProxySettingRegKey, true);
                    string proxyValue = string.Format("{0}:{1}", proxy.IP, proxy.Port);
                    string oldProxy = (string)reg.GetValue(ProxyServerRegKey);
                    reg.SetValue(ProxyServerRegKey, proxyValue, RegistryValueKind.String);
                    RegistryKey regConnections = reg.OpenSubKey("Connections", true);
                    byte[] defaultSettings = (byte[])regConnections.GetValue(DefaultConnectionSettingRegKey);
                    if (!string.IsNullOrEmpty(oldProxy))
                    {
                        regConnections.SetValue(DefaultConnectionSettingRegKey,
                            Encoding.ASCII.GetBytes(Encoding.ASCII.GetString(defaultSettings).Replace(oldProxy, proxyValue)));
                    }
                    lblCurrentProxyValue.Text = proxyValue;
                    regConnections.Close();
                    reg.Close();
                    NotifyRegKeyChanges();
                }
            }
        }

        private void NotifyRegKeyChanges()
        {
            User32Utils.NotifySettingChange();
            WinINetUtils.NotifyOptionSettingChanges();
        }

        private void TestProxySpeed()
        {
            for (int i = 0; i < lvProxy.Items.Count; i++)
            {
                if (lvProxy.Items[i].SubItems[3].Text == StatusNotTested)
                {
                    Thread thread = new Thread(new ParameterizedThreadStart(ThreadTestProxy));
                    thread.Start(lvProxy.Items[i]);
                    //ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadTestProxy), lvProxy.Items[i]);
                }
            }
        }

        private void LoadCurrentProxy()
        {
            RegistryKey reg = Registry.CurrentUser.OpenSubKey(ProxySettingRegKey, false);
            object proxyEnabledValue = reg.GetValue(ProxyEnableRegKey);
            object proxyServerValue = reg.GetValue(ProxyServerRegKey);
            reg.Close();
            if (((int)proxyEnabledValue) > 0)
            {
                btnEnableProxy.Text = DisableProxyButtonText;
            }
            else
            {
                btnEnableProxy.Text = EnableProxyButtonText;
            }
            if (proxyServerValue != null)
            {
                lblCurrentProxyValue.Text = proxyServerValue.ToString();
            }
        }

        private void ThreadTestProxy(object stateInfo)
        {
            ListViewItem item = (ListViewItem)stateInfo;
            Proxy proxy = (item).Tag as Proxy;
            Stopwatch sw = new Stopwatch();
            if (proxy != null)
            {
                try
                {
                    using (WebClient wc = new WebClient())
                    {
                        WebProxy wp = new WebProxy(proxy.IP, int.Parse(proxy.Port));
                        wc.Proxy = wp;
                        sw.Start();
                        wc.DownloadString("http://www.bing.com");
                        sw.Stop();
                    }
                    this.Invoke((MethodInvoker)delegate
                    {
                        item.SubItems[3].Text = sw.ElapsedMilliseconds.ToString();
                    });
                }
                catch (Exception ex)
                {
                    this.Invoke((MethodInvoker)delegate
                    {
                        item.SubItems[3].Text = StatusNotAvailable;
                    });
                }
            }
        }

        private void DownloadProxy(int proxyPageIndex)
        {
            string url = "http://www.cnproxy.com/proxy{0}.html";

            string content = Download(string.Format(url, proxyPageIndex));
            if (!string.IsNullOrEmpty(content))
            {
                Collection<Proxy> proxies = ParseProxy(content);
                BindProxy(proxies);
            }
        }

        private void BindProxy(Collection<Proxy> proxies)
        {
            foreach (Proxy proxy in proxies)
            {
                ListViewItem item = new ListViewItem();
                item.Tag = proxy;
                item.SubItems[0].Text = proxy.Name;
                item.SubItems.Add(proxy.IP);
                item.SubItems.Add(proxy.Port);
                item.SubItems.Add(StatusNotTested);
                lvProxy.Items.Add(item);
            }
        }

        private Collection<Proxy> ParseProxy(string content)
        {
            Collection<Proxy> proxies = new Collection<Proxy>();
            MatchCollection matches = regex.Matches(content);
            foreach (Match match in matches)
            {
                string ip = regexIP.Match(match.Value).Value;
                string port = regexPort.Match(match.Value).Value.Replace(
                    "+r","8").Replace(
                    "+d","0").Replace(
                    "+c","1").Replace(
                    "+k","2").Replace(
                    "+w","6").Replace(
                    "+b","5").Replace(
                    "+i","7").Replace(
                    "+z","3").Replace(
                    "+l","9").Replace(
                    "+m","4");
                int nameStartIndex = match.Value.LastIndexOf("<td>") + 4;
                int nameEndIndex = match.Value.LastIndexOf("</td>");
                string name = match.Value.Substring(nameStartIndex, nameEndIndex - nameStartIndex);
                Proxy proxy = new Proxy();
                proxy.IP = ip;
                proxy.Port = port;
                proxy.Name = name;
                proxies.Add(proxy);
            }
            return proxies;
        }

        private static string Download(string uri)
        {
            string content = null;
            try
            {
                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
                request.Headers.Add("Accept-Charset", "utf-8;");
                request.Headers.Add("Accept-Encoding", "gzip");
                request.Headers.Add("Accept-Language", "en-US");
                //request.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)";
                request.UserAgent = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.91 Safari/537.11";
                request.AutomaticDecompression = DecompressionMethods.GZip;
                using (WebResponse response = request.GetResponse())
                {
                    Encoding encoding = response.ContentType.IndexOf("gbk", StringComparison.InvariantCultureIgnoreCase) > 0 ? Encoding.GetEncoding(936) : Encoding.Default;

                    Stream responseStream = response.GetResponseStream();
                    using (StreamReader reader = new StreamReader(responseStream, encoding))
                    {
                        content = reader.ReadToEnd();
                    }
                }
            }
            catch (Exception ex)
            {
            }
            return content;
        }

        class ListViewItemComparer : IComparer
        {
            private int columnIndex;
            public ListViewItemComparer()
            {
                columnIndex = 0;
            }
            public ListViewItemComparer(int column)
            {
                columnIndex = column;
            }
            public int Compare(object x, object y)
            {
                return String.Compare(((ListViewItem)x).SubItems[columnIndex].Text, ((ListViewItem)y).SubItems[columnIndex].Text);
            }
        }
    }

    public class Proxy
    {
        public string Name { get; set; }
        public string IP { get; set; }
        public string Port { get; set; }
        public string Speed { get; set; }
    }

    internal class User32Utils
    {
        #region USER32 Options
        static IntPtr HWND_BROADCAST = new IntPtr(0xffff);
        static IntPtr WM_SETTINGCHANGE = new IntPtr(0x001A);
        #endregion

        #region STRUCT
        enum SendMessageTimeoutFlags : uint
        {
            SMTO_NORMAL = 0x0000,
            SMTO_BLOCK = 0x0001,
            SMTO_ABORTIFHUNG = 0x0002,
            SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
        }
        #endregion

        #region Interop

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, UIntPtr lParam, SendMessageTimeoutFlags fuFlags, uint uTimeout, out UIntPtr lpdwResult);
        #endregion


        internal User32Utils() { }

        internal static void NotifySettingChange()
        {
            UIntPtr result;
            SendMessageTimeout(HWND_BROADCAST, (uint)WM_SETTINGCHANGE, UIntPtr.Zero, UIntPtr.Zero, SendMessageTimeoutFlags.SMTO_NORMAL, 1000, out result);
        }

    }

    internal class WinINetUtils
    {

        #region WININET Options
        private const uint INTERNET_PER_CONN_PROXY_SERVER = 2;
        private const uint INTERNET_PER_CONN_PROXY_BYPASS = 3;
        private const uint INTERNET_PER_CONN_FLAGS = 1;

        private const uint INTERNET_OPTION_REFRESH = 37;
        private const uint INTERNET_OPTION_PROXY = 38;
        private const uint INTERNET_OPTION_SETTINGS_CHANGED = 39;
        private const uint INTERNET_OPTION_END_BROWSER_SESSION = 42;
        private const uint INTERNET_OPTION_PER_CONNECTION_OPTION = 75;

        private const uint PROXY_TYPE_DIRECT = 0x1;
        private const uint PROXY_TYPE_PROXY = 0x2;

        private const uint INTERNET_OPEN_TYPE_PROXY = 3;
        #endregion

        #region STRUCT
        struct Value1
        {
            uint dwValue;
            string pszValue;
            FILETIME ftValue;
        };

        [StructLayout(LayoutKind.Sequential)]
        struct INTERNET_PER_CONN_OPTION
        {
            uint dwOption;
            Value1 Value;
        };

        [StructLayout(LayoutKind.Sequential)]
        struct INTERNET_PER_CONN_OPTION_LIST
        {
            uint dwSize;
            [MarshalAs(UnmanagedType.LPStr, SizeConst = 256)]
            string pszConnection;
            uint dwOptionCount;
            uint dwOptionError;
            IntPtr pOptions;

        };

        [StructLayout(LayoutKind.Sequential)]
        struct INTERNET_CONNECTED_INFO
        {
            int dwConnectedState;
            int dwFlags;
        };
        #endregion

        #region Interop
        [DllImport("wininet.dll", EntryPoint = "InternetSetOptionA", CharSet = CharSet.Ansi, SetLastError = true, PreserveSig = true)]
        private static extern bool InternetSetOption(IntPtr hInternet, uint dwOption, IntPtr pBuffer, int dwReserved);
        #endregion

        internal WinINetUtils() { }


        internal static void NotifyOptionSettingChanges()
        {
            InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
            InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
        }

    }
}
